package ar.com.solidussource.framework.backend.dao.crud.impl.hibernate;

import java.io.Serializable;
import java.sql.SQLException;
import java.util.List;

import javax.persistence.PersistenceException;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Projections;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import ar.com.solidussource.framework.backend.dao.crud.GenericCRUDDao;
import ar.com.solidussource.framework.backend.dao.definition.DataAccessObject;
import ar.com.solidussource.framework.backend.dao.filter.Filter;
import ar.com.solidussource.framework.backend.dao.filter.Page;
import ar.com.solidussource.framework.backend.dao.filter.Result;

/**
 * Implementacion del dao generico utilizando hibernate como framework base de
 * implementacion.<br>
 * Esta implementacion utiliza el
 */
public class GenericCRUDDaoHIBImpl<T> extends HibernateDaoSupport implements InitializingBean, GenericCRUDDao<T>, DataAccessObject {

	private static final Logger logger = Logger.getLogger(GenericCRUDDaoHIBImpl.class);

	private Class<T> type = null;

	public void delete(T instance) throws PersistenceException {
		logger.debug("deleting persistence instance " + instance);
		this.getHibernateTemplate().delete(instance);

	}

	@SuppressWarnings("unchecked")
	public List<T> getAll() throws PersistenceException {
		logger.debug("loading all instance of [" + this.getType().getName() + "]");
		return this.getHibernateTemplate().loadAll(this.getType());
	}

	@SuppressWarnings("unchecked")
	public T getById(Serializable id) throws PersistenceException {
		logger.debug("loading by id update hibernate [" + this.getType().getName() + "] id[" + id + "]");
		return (T) this.getHibernateTemplate().get(this.getType(), id);

	}

	public T saveNew(T instance) throws PersistenceException {
		logger.debug("saving new hibernate [" + instance + "]");
		this.getHibernateTemplate().save(instance);
		return instance;
	}

	public T saveOrUpdate(T instance) throws PersistenceException {
		logger.debug("saving or updating hibernate [" + instance + "]");
		this.getHibernateTemplate().saveOrUpdate(instance);
		return instance;
	}

	/**
	 * @return the type
	 */
	public Class<T> getType() {
		return type;
	}

	/**
	 * @param type
	 *            the type to set
	 */
	public void setType(Class<T> type) {
		this.type = type;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * ar.com.osde.framework.persistence.dao.crud.GenericCRUDDao#getAll(ar.com
	 * .osde.framework.persistence.dao.filter.Page)
	 */

	public Result<T> getAll(final Page page) throws PersistenceException {
		logger.debug("Recuperando pagina completa sin filtrar para[" + type + "]");
		return this.getAll(null, page);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * ar.com.osde.framework.persistence.dao.crud.GenericCRUDDao#getAll(ar.com
	 * .osde.framework.persistence.dao.filter.Filter,
	 * ar.com.osde.framework.persistence.dao.filter.Page)
	 */
	@SuppressWarnings("unchecked")
	public Result<T> getAll(final Filter filter, final Page page) throws PersistenceException {
		Result<T> result = (Result<T>) this.execute(new HibernateCallback() {
			public Object doInHibernate(Session session) throws HibernateException, SQLException {
				logger.debug("Recuperando pagina [" + page.getPageNumber() + "] para type[" + type.getSimpleName() + "]");
				logger.debug("First Result: "+(  (page.getPageNumber() - 1)  * page.getPageSize()));
				logger.debug("Max Results "+page.getPageSize());
				Criteria cr = session.createCriteria(type);
				cr.setFirstResult(  (page.getPageNumber() - 1) * page.getPageSize());
				cr.setMaxResults(page.getPageSize());
				if (filter != null) {
					logger.debug("Filtrando criteria para [" + type + "] con filter[" + filter + "]");
					filter.fillCriteria(cr);
					filter.fillCriteriaNotPagination(cr);
				}
				List<T> results = cr.list();
				logger.debug("Se recuperaron "+results.size() +" Resultados");
				Result<T> result = new Result<T>();
				logger.debug("Ejecutando consulta paginada [" + type + "]");
				result.setResult(results);
				result.setPage(new Page(page.getPageNumber(), page.getPageSize()));

				return result;
			}
		});
		result.setTotalResults(this.count(filter));
		return result;
	}

	/**
	 * Ejecuta el callback de hibernate utilizando el hibernate template y
	 * realiza la transformacion de excecpciones en caso de ser necesario
	 * 
	 * @param callback
	 *            a ser ejecutado dentro del template
	 * @return Object de resultado
	 */
	protected Object execute(HibernateCallback callback) throws PersistenceException {
		try {
			return this.getHibernateTemplate().execute(callback);
		} catch (DataAccessException e) {
			logger.error("Error en dao generico " + e.getMessage(), e);
			throw new PersistenceException("Error en dao generico " + e.getMessage(), e);
		}

	}

	/* (non-Javadoc)
	 * @see ar.com.osde.framework.persistence.dao.crud.GenericCRUDDao#count(ar.com.osde.framework.persistence.dao.filter.Filter)
	 */
	public long count(final Filter filter) throws PersistenceException {
		return (Long)this.execute(new HibernateCallback() {
			public Object doInHibernate(Session session) throws HibernateException, SQLException {
				logger.debug("Calculando total resultados [" + type + "]");
				Criteria cr = session.createCriteria(type).setProjection(Projections.rowCount());
				if (filter != null) {
					logger.debug("Filtrando criteria de count para [" + type + "] con filter[" + filter + "]");
					filter.fillCriteria(cr);
				}
				Long count = (Long) cr.uniqueResult();
				return count;
			}
		});

	}
}